/*
 * Copyright (c) 2010 All Inclusive Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.allinclusive.fw.commons.util;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.allinclusive.fw.commons.util.ImperialCalendar.WAREKI_MODE;

/**
 * 和暦を扱う為のユーティリティーメソッドを提供します。
 * 
 */
public class ImperialCalendarUtil {
	
    /**
     * ログインスタンス
     */
	private static final Log log = LogFactory.getLog(ImperialCalendarUtil.class);

	/**
	 * デフォルトの和暦フォーマット(yyMMdd)
	 */
	public static final String DEFAULT_WAREKI_FORMAT = "yyMMdd";

	/**
	 * デフォルトの西暦フォーマット(yyyyMMdd)
	 */
	public static final String DEFAULT_SEIREKI_FORMAT = "yyyyMMdd";

	/**
	 * <p>
	 * 元号コードと和暦文字列から、<code>Date</code>を生成します
	 * </p>
	 * 和暦文字列は、"yyMMdd"形式である必要があります。
	 * 
	 * @param eraCode 元号コード
	 * @param wareki 和暦文字列
	 * @return <code>Date</code>
	 * 
	 */
	public static Date warekiToDate(String eraCode, String wareki, WAREKI_MODE mode) {

		if (StringUtils.length(wareki) != 6) {
			throw new ImperialCalendarException("入力された和暦文字列[" + wareki
					+ "]は'yyMMdd'形式ではありません");
		}
		ImperialCalendar impCal = new ImperialCalendar(eraCode, wareki, mode);
		return impCal.getDate();
	}

	/**
	 * <p>
	 * 元号コードと和暦文字列から、<code>Date</code>を生成します
	 * </p>
	 * 和暦文字列は、"yyMMdd"形式である必要があります。
	 * 
	 * @param eraCode 元号コード
	 * @param wareki 和暦文字列
	 * @return <code>Date</code>
	 * 
	 */
	public static Date warekiToDate(String eraCode, String wareki) {
		return warekiToDate(eraCode, wareki, WAREKI_MODE.YMD);
	}

	/**
	 * 西暦から、その年の年号のコード値を取得します。 引数として指定する西暦のフォーマットは{@link #DEFAULT_SEIREKI_FORMAT}であるものとして処理されます。
	 * 
	 * @param seireki 西暦文字列
	 * @return 該当する年号のコード値
	 */
	public static String getEraCode(String seireki, WAREKI_MODE mode) {
		return getEraCode(seireki, DEFAULT_SEIREKI_FORMAT, mode);
	}

	/**
	 * 西暦から、その年の和暦のコード値を取得する
	 * 
	 * @param seireki 西暦文字列
	 * @param format 西暦文字列の書式
	 * @return 指定された年月日に対応する年号のコード値
	 */
	public static String getEraCode(String seireki, String format,
			WAREKI_MODE mode) {

		// 西暦文字列が空白かNullの場合は空白文字列を返す
		if (StringUtils.isEmpty(seireki)) {
			return StringUtils.EMPTY;
		}

		try {
			return getEraCode(DateUtils.parseDate(seireki, new String[] { format }), mode);
		} catch (ParseException e) {
			throw new ImperialCalendarException("指定された西暦[" + seireki + "]もしくは書式[" + format + "]が不正です", e);
		}
	}

	/**
	 * <code>Date</code>から、その年の和暦のコード値を取得する
	 * 
	 * @param date Date
	 * @return 指定された年月日に対応する年号のコード値
	 */
	public static String getEraCode(Date date, WAREKI_MODE mode) {
		Date tmp = date;

		//年月モードでの判定だった場合、月末日での判定とする
		if (mode == WAREKI_MODE.YM) {
			tmp = DateUtils.addDays(DateUtils.truncate(DateUtils.addMonths(
					date, 1), Calendar.MONTH), -1);
		}

		if (log.isTraceEnabled()) {
			DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
			log.trace("元号取得対象日:" + df.format(tmp));
		}

		ImperialCalendar impCal = new ImperialCalendar(tmp);
		return impCal.getEraCode();
	}

	/**
	 * コード値と和暦文字列から、西暦文字列を取得します。 指定する和暦文字列のフォーマットは"yyMMdd"である必要があります。
	 * また、取得される西暦のフォーマットは"yyyyMMdd"になります。
	 * 
	 * @param eraCode 年号のコード値
	 * @param wareki 和暦文字列
	 * @return 西暦文字列
	 */
	public static String getSeirekiString(String eraCode, String wareki) {
		return getSeirekiString(eraCode, wareki, DEFAULT_SEIREKI_FORMAT,
				WAREKI_MODE.YMD);
	}

	/**
	 * コード値と和暦文字列から、西暦文字列を取得します。 指定する和暦文字列のフォーマットは"yyMMdd"である必要があります。
	 * また、取得される西暦のフォーマットは"yyyyMMdd"になります。
	 * 
	 * @param eraCode 年号のコード値
	 * @param wareki 和暦文字列
	 * @return 西暦文字列
	 */
	public static String getSeirekiString(String eraCode, String wareki,
			WAREKI_MODE mode) {
		return getSeirekiString(eraCode, wareki, DEFAULT_SEIREKI_FORMAT, mode);
	}

	/**
	 * <p>
	 * 元号コードと和暦文字列から、西暦文字列を生成します。
	 * </p>
	 * 和暦文字列は、"yyMMdd"形式である必要があります。
	 * <p>
	 * 例
	 * </p>
	 * <ul>
	 * <li>ImperialCalendarUtil.getWarekiString("03", "551101", "yyyyMMdd"); ⇒
	 * "19801101"</li>
	 * </ul>
	 * 
	 * @param eraCode
	 *            元号コード
	 * @param wareki
	 *            和暦文字列
	 * @param format
	 *            取得する西暦のフォーマット
	 * @return 西暦文字列
	 */
	public static String getSeirekiString(String eraCode, String wareki,
			String format, WAREKI_MODE mode) {

		// 元号コード、もしく和暦文字列が空白、もしくはNullだった場合は空白文字列を返す
		if (StringUtils.isEmpty(eraCode) || StringUtils.isEmpty(wareki)) {
			return StringUtils.EMPTY;
		}

		// 和暦文字列の妥当性チェック
		if (StringUtils.length(wareki) != 6) {
			throw new ImperialCalendarException("入力された和暦文字列[" + wareki
					+ "]は'yyMMdd'形式ではありません");
		}

		SimpleDateFormat sdf = new SimpleDateFormat(format);
		return sdf.format(warekiToDate(eraCode, wareki, mode));
	}

	/**
	 * <p>
	 * 指定した<code>Date</code>を表す和暦文字列を取得します
	 * </p>
	 * 取得する和暦文字列の書式は{@link ImperialCalendar#format(String)}の書式と同等です。
	 * 
	 * @param date
	 *            <code>Date</code>
	 * @param format
	 *            取得する和暦の書式
	 * @return 和暦文字列
	 */
	public static String getWarekiString(Date date, String format) {
		ImperialCalendar impCal = new ImperialCalendar(date);
		return impCal.format(format);
	}

	/**
	 * <p>
	 * 指定した西暦文字列から和暦文字列を取得します
	 * </p>
	 * 指定する西暦文字列は"yyyyMMdd"形式である必要があります。 取得する和暦文字列は"yyMMdd"形式です。
	 * <p>
	 * 例
	 * </p>
	 * <ul>
	 * <li>ImperialCalendarUtil.getWarekiString("19801101"); ⇒ "551101"</li>
	 * </ul>
	 * 
	 * @param seireki 西暦文字列
	 * @return "yyMMdd"形式の和暦文字列
	 */
	public static String getWarekiString(String seireki, WAREKI_MODE mode) {
		return getWarekiString(seireki, DEFAULT_SEIREKI_FORMAT,
				DEFAULT_WAREKI_FORMAT, mode);
	}

	/**
	 * <p>
	 * 指定した西暦文字列から和暦文字列を取得します
	 * </p>
	 * 
	 * @param seireki 西暦文字列
	 * @param seirekiFormat 西暦のフォーマット
	 * @param warekiFormat 和暦のフォーマット
	 * @return 指定和暦のフォーマット形式の和暦文字列
	 */
	public static String getWarekiString(String seireki, String seirekiFormat,
			String warekiFormat) {
		return getWarekiString(seireki, seirekiFormat, warekiFormat,
				WAREKI_MODE.YMD);
	}

	/**
	 * <p>
	 * 西暦文字列を和暦文字列に変換します
	 * </p>
	 * 西暦文字列の書式は{@link SimpleDateFormat}の書式と同等です。 取得する和暦文字列の書式は{@link ImperialCalendar#format(String)}の書式と同等です。
	 * <ul>
	 * <li>ImperialCalendarUtil.getWarekiString("19801101", "yyyyMMdd",
	 * "GGyyMMdd"); ⇒ "S551101"</li>
	 * <li>ImperialCalendarUtil.getWarekiString("19801101", "yyyyMMdd",
	 * "GGGGyy年MM月dd日"); ⇒ "昭和55年11月01日"</li>
	 * </ul>
	 * 
	 * @param seireki
	 *            西暦文字列
	 * @param seirekiFormat
	 *            西暦文字列の書式
	 * @param warekiFormat
	 *            取得する和暦文字列の書式
	 * @return 指定した書式の和暦文字列
	 * 
	 * @see SimpleDateFormat
	 * @see ImperialCalendar#format(String)
	 */
	public static String getWarekiString(String seireki, String seirekiFormat,
			String warekiFormat, WAREKI_MODE mode) {

		// 与えられた西暦が空白、もしくはNullだった場合は空文字列を返す
		if (StringUtils.isEmpty(seireki)) {
			return StringUtils.EMPTY;
		}

		try {
			Date date = DateUtils.parseDate(seireki,
					new String[] { seirekiFormat });

			//年月モードの場合は月末日とする
			if (mode == WAREKI_MODE.YM) {
				date = DateUtils.addDays(DateUtils.truncate(DateUtils
						.addMonths(date, 1), Calendar.MONTH), -1);
			}

			ImperialCalendar impCal = new ImperialCalendar(date);
			return impCal.format(warekiFormat);
		} catch (ParseException e) {
			throw new ImperialCalendarException("和暦への変換に失敗しました[西暦文字列:"
					+ seireki + " 西暦書式:" + seirekiFormat + " 和暦書式:"
					+ warekiFormat + "]", e);
		}
	}
}
